package com.devbobcorn.nekoration.entities;

import java.util.List;

import javax.annotation.Nullable;

import com.devbobcorn.nekoration.items.ModItems;
import com.devbobcorn.nekoration.network.ModPacketHandler;
import com.devbobcorn.nekoration.network.S2CUpdateWallpaperPart;
import com.devbobcorn.nekoration.utils.TagTypes;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;

import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.item.HangingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.DyeColor;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.ListNBT;
import net.minecraft.network.IPacket;
import net.minecraft.network.PacketBuffer;
import net.minecraft.tileentity.BannerPattern;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.SoundEvents;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;
import net.minecraftforge.fml.network.FMLPlayMessages;
import net.minecraftforge.fml.network.NetworkHooks;
import net.minecraftforge.fml.network.PacketDistributor;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;

public class WallPaperEntity extends HangingEntity implements IEntityAdditionalSpawnData {
	@Nullable
	private DyeColor baseColor = DyeColor.WHITE;
	@Nullable
	private ListNBT itemPatterns;
	private boolean receivedData = false;
	@Nullable
	private List<Pair<BannerPattern, DyeColor>> patterns = null;

    public enum Part {
        UPPER((byte) 0, "upper"),
        LOWER((byte) 1, "lower"),
        FULL((byte) 2, "full");

        Part(byte id, String name){
			this.id = id;
            this.name = name;
        }

		public static Part fromId(byte id){
			for (Part p : Part.values()) {
				if (p.id == id)
					return p;
			}
			return Part.FULL;
		}

		public final byte id;
        public final String name;
    }

    private Part part = Part.FULL;

	protected WallPaperEntity(EntityType<WallPaperEntity> type, World world) {
		// Constructor 1: the default one, but not used to create instances in worlds
		super(ModEntityType.WALLPAPER_TYPE, world);
	}

	public WallPaperEntity(World world, BlockPos pos, Direction dir, ItemStack stack, Part part) {
		// Constructor 2: the one for server-side to create WallPaperEntity Objects
		super(ModEntityType.$WALLPAPER_TYPE.get(), world, pos);
		this.part = part;
		this.setDirection(dir);
		fromItem(stack);
	}

	public WallPaperEntity(FMLPlayMessages.SpawnEntity packet, World world) {
		// Constructor 3: the one for client-side, creating instances with data packets from the Server
		// Enable by adding 'setCustomClientFactory' when building the entity type
		super(ModEntityType.WALLPAPER_TYPE, world);
	}

	public void setPart(Part part){
		this.part = part;
	}

	public Part getPart(){
		return this.part;
	}

	@Nullable
	public static ListNBT getPatterns(CompoundNBT tag) {
		ListNBT listnbt = null;
		if (tag.contains("Patterns", TagTypes.LIST_NBT_ID)) {
			listnbt = tag.getList("Patterns", TagTypes.COMPOUND_NBT_ID).copy();
		}
		return listnbt;
	}

	@Nullable
	public static DyeColor getBaseColor(CompoundNBT tag) {
		if (tag.contains("Base", TagTypes.INT_NBT_ID)) {
			return DyeColor.byId(tag.getInt("Base"));
		}
		return DyeColor.WHITE;
	}

	public DyeColor getBaseColor(){
		return this.baseColor;
	}
	
	public ItemStack getItem() {
		ItemStack itemstack = new ItemStack(ModItems.WALLPAPER.get());
		CompoundNBT tag = itemstack.getOrCreateTagElement("BlockEntityTag");
		
		if (this.itemPatterns != null && !this.itemPatterns.isEmpty()) {
			tag.put("Patterns", this.itemPatterns.copy());
			tag.putInt("Base", this.baseColor.getId());
		} else if (this.baseColor != DyeColor.WHITE)
			tag.putInt("Base", this.baseColor.getId());
		return itemstack;
	}

	public void fromItem(ItemStack stack) {
		CompoundNBT tag = stack.getTagElement("BlockEntityTag");
		if (tag != null) {
			this.itemPatterns = getPatterns(tag);
			this.baseColor = getBaseColor(tag);
			this.patterns = null;
			this.receivedData = true;
		}
	}

	public ActionResultType interact(PlayerEntity player, Hand hand) {
		if (this.part == Part.LOWER)
			this.part = Part.UPPER;
		else if (this.part == Part.UPPER)
			this.part = Part.LOWER;
        
        // Send update packet if this is server
        World world = player.level;
        if (!world.isClientSide) {
            final S2CUpdateWallpaperPart packet = new S2CUpdateWallpaperPart(this.getId(), this.part.id);
            ModPacketHandler.CHANNEL.send(PacketDistributor.ALL.noArg(), packet);
        }

		return ActionResultType.SUCCESS;
	}

	@Override
	public void addAdditionalSaveData(CompoundNBT tag) {
		tag.putByte("Facing", (byte) direction.get2DDataValue());
		tag.putByte("Part", part.id);
		tag.putInt("Base", this.baseColor.getId());
		if (this.itemPatterns != null) {
			tag.put("Patterns", this.itemPatterns);
		}
		super.addAdditionalSaveData(tag);
	}

	@Override
	public void readAdditionalSaveData(CompoundNBT tag) {
		this.direction = Direction.from2DDataValue((int) (tag.getByte("Facing")));
		this.part = Part.fromId(tag.getByte("Part"));
		if (tag.contains("Base", TagTypes.INT_NBT_ID))
			this.baseColor = DyeColor.byId(tag.getInt("Base"));
		else this.baseColor = DyeColor.WHITE;
		if (tag.contains("Patterns", TagTypes.LIST_NBT_ID)) {
			this.itemPatterns = tag.getList("Patterns", TagTypes.COMPOUND_NBT_ID);
			this.patterns = null;
			this.receivedData = true;
		}
		super.readAdditionalSaveData(tag);
		this.setDirection(this.direction);
	}

	@OnlyIn(Dist.CLIENT)
	public List<Pair<BannerPattern, DyeColor>> getPatterns() {
		if (this.patterns == null && this.receivedData) {
			this.patterns = createPatterns(this.baseColor, this.itemPatterns);
		}
		return this.patterns;
	}

	@OnlyIn(Dist.CLIENT)
	public static List<Pair<BannerPattern, DyeColor>> createPatterns(DyeColor baseColor, @Nullable ListNBT tag) {
		List<Pair<BannerPattern, DyeColor>> list = Lists.newArrayList();
		list.add(Pair.of(BannerPattern.BASE, baseColor));
		if (tag != null) {
			for (int i = 0; i < tag.size(); ++i) {
				CompoundNBT compoundnbt = tag.getCompound(i);
				BannerPattern bannerpattern = BannerPattern.byHash(compoundnbt.getString("Pattern"));
				if (bannerpattern != null) {
					int j = compoundnbt.getInt("Color");
					list.add(Pair.of(bannerpattern, DyeColor.byId(j)));
				}
			}
		}
		return list;
	}

	@Override
	public int getWidth() {
		return 14;
	}

	@Override
	public int getHeight() {
		return (part == Part.FULL) ? 32 : 16;
	}

	@Override
	public ItemStack getPickedResult(RayTraceResult target) {
		return getItem();
	}

	@Override
	public void dropItem(Entity entity) {
		if (this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
			this.playSound(SoundEvents.BOOK_PAGE_TURN, 1.0F, 1.0F);
			if (entity instanceof PlayerEntity) {
				PlayerEntity playerentity = (PlayerEntity) entity;
				if (playerentity.abilities.instabuild) {
					return;
				}
			}
			this.spawnAtLocation(getItem());
		}
	}

	@Override
	public void playPlacementSound() {
		this.playSound(SoundEvents.BOOK_PUT, 1.0F, 1.0F);
	}

	@Override
	public IPacket<?> getAddEntityPacket() {
		IPacket<?> packet = NetworkHooks.getEntitySpawningPacket(this);
		return packet;
	}

	@Override
	public void writeSpawnData(PacketBuffer buffer) {
		// We cannot get the same bounding box output with different position inputs,
		// because server makes adjustments to that. As a result, we need to pass the
		// right position and hitbox straight over to the clients...
		buffer.writeByte(this.part.id);
		buffer.writeDouble(this.position().x);
		buffer.writeDouble(this.position().y);
		buffer.writeDouble(this.position().z);
		buffer.writeBlockPos(this.blockPosition());
		buffer.writeByte(direction.get2DDataValue());
		buffer.writeDouble(this.getBoundingBox().minX);
		buffer.writeDouble(this.getBoundingBox().minY);
		buffer.writeDouble(this.getBoundingBox().minZ);
		buffer.writeDouble(this.getBoundingBox().maxX);
		buffer.writeDouble(this.getBoundingBox().maxY);
		buffer.writeDouble(this.getBoundingBox().maxZ);
		CompoundNBT tag = new CompoundNBT();
		tag.putInt("Base",this.baseColor.getId());
		if (this.itemPatterns != null)
			tag.put("Patterns", this.itemPatterns);
		buffer.writeNbt(tag);
	}

	@Override
	public void readSpawnData(PacketBuffer additionalData) {
		// Client receives...
		this.part = Part.fromId(additionalData.readByte());
		this.setPosRaw(additionalData.readDouble(), additionalData.readDouble(), additionalData.readDouble());
		this.pos = additionalData.readBlockPos();
		byte dir = additionalData.readByte();
		//this.setDirection(Direction.from2DDataValue(dir)); This will call recalcuateBoundingBox, which isn't what we want
		this.direction = Direction.from2DDataValue(dir);
		this.yRot = (float) (this.direction.get2DDataValue() * 90);
		this.yRotO = this.yRot;
		//this.recalculateBoundingBox(); To use this we'll need the original position data, which we don't have on the client-side
		this.setBoundingBox(new AxisAlignedBB(additionalData.readDouble(), additionalData.readDouble(), additionalData.readDouble(), additionalData.readDouble(), additionalData.readDouble(), additionalData.readDouble()));
		CompoundNBT tag = additionalData.readNbt();
		if (tag.contains("Base", TagTypes.INT_NBT_ID))
			this.baseColor = DyeColor.byId(tag.getInt("Base"));
		if (tag.contains("Patterns", TagTypes.LIST_NBT_ID)) {
			this.itemPatterns = tag.getList("Patterns", TagTypes.COMPOUND_NBT_ID);
			this.patterns = null;
			this.receivedData = true;
		}
	}
}
